[Payment due @thelullabyy] Vendor matching CC: Add Vendors tab to Workspace settings#96143
Conversation
Adds a read-only Vendors tab to Workspace settings for workspaces with the `vendorMatching` beta enabled and either QBO (Credit/Debit-card export) or Sage Intacct (Credit Card Charge export) connected. Sourced from the existing `connections.<name>.data.vendors` Onyx data used by the vendor selector RHP shipped in #91886, so no backend changes are required. Gate: `hasVendorFeature(policy, isBetaEnabled(BETAS.VENDOR_MATCHING)) && isMatchingVendorListLoaded(policy)`. The list-loaded clause is stricter than the inline surfaces (Default vendor row, per-expense Vendor field) because a read-only externally-synced list with no user CTA is a dead end if it opens empty. Phase 2 (GL code column for QBO) requires a Java `AcctNum` fetch in `QuickbooksOnlineImporter.java` and follows in a separate change. Intacct is fully complete under Phase 1 (no GL code in Intacct vendor imports). See Expensify/Expensify#650595 for the full plan.
|
@Krishna2323 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 79c5adcdb4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- Gate the Vendors menu row on isPolicyAdmin(policy) so it mirrors the page's [ADMIN, PAID] accessVariants. Without this, non-admin members saw the menu row but tap-through rendered NotFound. - Add SCREENS.WORKSPACE.VENDORS to WIDE_LAYOUT_INACTIVE_SCREENS so the keyboard focus trap is disabled on wide side-by-side layouts, matching every other workspace central-pane screen.
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
|
@codex review |
|
@Beamanator does this require C+ review? |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f6a5d12c05
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- Fix isMatchingVendorListLoaded so it returns false for Intacct workspaces that haven't synced yet. Previously the Intacct branch of getActiveVendorMatchingVendors normalized a missing data.vendors to [], which meant the helper returned an array (not undefined) and the tab gate opened onto an empty page — the exact pre-sync state we're trying to avoid. QBO was already correct because it returned data?.vendors directly. - Set shouldHideSelectionButton on the Vendors page rows so SelectableListItem skips the radio button. The list is read-only, so showing radio buttons with a no-op onSelectRow made rows look like editable pickers with no feedback on tap.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4d184b98dd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@Krishna2323 hey hey! I think yes, do you have experience with setting up QBO / other accounting integrations? If so, can you let me know an email address you have for testing this so i can add it to the beta for you to test? 🙏 If not, no worries i can find someone else to help test this! |
Only the active policy has connections data at app start (per withPolicyConnections's own docstring); other workspaces have to fetch it lazily. hasVendorFeature() and isMatchingVendorListLoaded() both read `policy.connections`, so on a non-active vendor-matching workspace the Vendors menu row was staying hidden even after the beta was flipped on, until the user visited the Accounting tab. Add an effect that mirrors withPolicyConnections' fetch trigger inline in WorkspaceInitialPage, gated on the VENDOR_MATCHING beta so we don't add an accounting-page fetch to every workspace visit for non-beta users. Same self-gates as withPolicyConnections: bails if connections are already fetched, or if the workspace has no connections enabled at all.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de45732ec3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The previous commit added a connections-prefetch effect to WorkspaceInitialPage so the sidebar tab renders on non-active workspaces, but that only covers the sidebar entry point. A user opening or refreshing /workspaces/:policyID/vendors directly on a non-active workspace still hit the page before connections were fetched, so hasVendorFeature() returned false and AccessOrNotFoundWrapper rendered NotFound. Wrap WorkspaceVendorsPage in withPolicyConnections so the page itself triggers the fetch and shows a loading indicator until connections are available. Also register VENDORS in the PolicyRouteName union used by withPolicy's route type, matching how CATEGORIES/TAGS/etc. are typed.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a9f0095593
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
useOnyx returns undefined for the value during initial hydration, so my guard on !hasConnectionsDataBeenFetched passed even when the persisted flag was actually true, causing a redundant openPolicyAccountingPage call on every workspace-page rehydrate. Mirror withPolicyConnections by reading the second-tuple result metadata and gating on isLoadingOnyxValue.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2ab6b5b010
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- usePolicyConnectionsPrefetch: drop the specific caller reference (WorkspaceInitialPage was named as an example) so the doc doesn't drift as consumers change. Focus on what the hook does and its safety guards. - WorkspaceInitialPage: trim the multi-clause explanation into a shorter block that names the read-through it protects against without narrating the hypothetical failure mode.
Auto-generated refinements from OSBotify in #96143 (comment). Covers de, es, it, ja, nl, pl, pt-BR, zh-hans. Notable fixes: - de: 'Anbieter/Lieferanten' consistency + preposition fixes - ja: 'ベンダー' -> '取引先' where more natural for business context - zh-hans: preposition tweak on managedInAccountingSoftware
…ent fix Hook subscribed under `..._undefined` when policy was still hydrating. Fall back to a stable empty-string key and treat the loading flag as false in that state so we don't gate off a real fetch on the first real policy render. Also fix the WorkspaceInitialPage comment: Accounting isn't the only page that triggers withPolicyConnections' fetch — any wrapped page does.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0f86474c85
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
WorkspaceVendorsPage sourced the 'managed in' subtitle from getConnectedIntegration/getCurrentConnectionName, which pick the first accounting connection in ACCOUNTING_CONNECTION_NAMES. When Sage Intacct is the active vendor-matching source but a stale QBO/Xero connection object is still attached, the header rendered the wrong integration while the rows below came from Intacct. New getActiveVendorMatchingIntegration helper (sibling of getActiveVendorMatchingVendors) returns the connection whose export mode is actually scoping vendor matching. Page uses it for both the icon and the display name so the header and rows can't drift.
|
@codex review |
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
rulesdir/no-default-id-values forbids '' fallbacks in Onyx keys. The shared helper returns undefined when the ID is empty, which useOnyx treats as 'skip subscription' — same intent as the empty-string placeholder but conforming to the style guide.
|
Back to you again @thelullabyy 🙏 |
|
@marcochavezf Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
🎯 @thelullabyy, thanks for reviewing and testing this PR! 🎉 A payment issue will be created for your review once this PR is deployed to production. If payment is not needed (e.g., regression PR review fix etc), react with 👎 to this comment to prevent the payment issue from being created. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 marcochavezf has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/marcochavezf in version: 9.4.41-0 🚀
|
Help site review — no changes required (yet)I reviewed this PR against the help site articles in Why:
Recommended follow-up: when If you'd prefer I create the draft docs PR now anyway (e.g. to stage the article ahead of GA), reply and I'll put one together. @Beamanator — since no help site PR was created, there's nothing to review yet. If you'd like the article staged now, reply and I'll open a draft PR for you to review and mark |
|
looking good in staging! |
|
🤖 Payment issue created: #96708 |
|
🚀 Deployed to production by https://github.com/yuwenmemon in version: 9.4.41-4 🚀
Bundle Size Analysis (Sentry): |
|
Also noticed that the (locked) Vendor toggle is missing from More features per the design doc https://docs.google.com/document/d/1Q1n6e7-LcXchKidSRtFMbWkLwN8tY5lvShJjg5joRmo/edit?tab=t.0#heading=h.o44xeklr8ms4
|





Explanation of Change
Adds a read-only Vendors tab under Workspace settings for workspaces on the
vendorMatchingbeta with either QBO (Credit/Debit-card export) or Sage Intacct (Credit Card Charge export) connected. The list is sourced from the same Onyx data already used by the vendor selector RHP shipped in App #91886, so this change is App-only — no backend work is required. The tab shows one column (vendor name); the GL code column for QBO is a follow-up that also needs a JavaAcctNumfetch inQuickbooksOnlineImporter.java— tracked in the same Expensify #650595 as Phase 2. Intacct never has a GL code so it is fully complete under Phase 1.The visibility gate is
canReadPolicyFeature(POLICY_FEATURE.VENDORS) && hasVendorFeature(policy, isBetaEnabled(BETAS.VENDOR_MATCHING)) && isMatchingVendorListLoaded(policy). The role check reusesROLE_PERMISSION_BUNDLESinPolicyUtils.ts, which derives the ADMIN/AUDITOR bundles fromObject.values(CONST.POLICY.POLICY_FEATURE)— addingPOLICY_FEATURE.VENDORSgives admins WRITE and auditors READ automatically, no server-side change required. The list-loaded clause is stricter than the inline surfaces (Default vendor row, per-expense Vendor field) because a read-only externally-synced destination with no user CTA reads as broken if it opens empty; the page still ships a belt-and-braces empty state for the edge case where the tab is reached before the list populates.For non-active workspaces the effect that triggers
openPolicyAccountingPageruns inWorkspaceInitialPage, gated on the same read-access check, and the page itself is wrapped inwithPolicyConnectionsso direct-URL access hydrates connections before evaluating the wrapper. Offline and Onyx-hydration-race cases are handled by mirroringwithPolicyConnections'sisOfflineandisLoadingOnyxValueguards.Fixed Issues
$ https://github.com/Expensify/Expensify/issues/650595
PROPOSAL: N/A (internal feature, no external proposal)
Tests
Prerequisites: a workspace with the
vendorMatchingbeta enabled and either (a) QBO connected with non-reimbursable export destination set to Credit Card or Debit Card, or (b) Sage Intacct connected with Credit Card Charge export.Happy path
Visibility branches
vendorMatchingbeta for the test user and verify the Vendors row is hidden regardless of export type.Role-based access (Control workspace)
POLICY.POLICY_FEATURE.VENDORSauto-picks up READ for Auditors viaROLE_PERMISSION_BUNDLESinPolicyUtils.ts.VENDORSin their permission bundle).Edge cases
isMatchingVendorListLoadedclause of the gate). After the sync completes, the row appears.policy.connectionsisn't hydrated on app start): paste/workspaces/<policyID>/vendorsdirectly into the URL bar. Verify the page shows a loading indicator untilwithPolicyConnectionshydrates the connections, then renders the list.Offline tests
policy.connectionsisn't hydrated), keep the network disabled and open Workspace settings; verify the Vendors row is hidden. Re-enable the network and verify the row appears without requiring a manual navigation to Accounting — the effect refires on theisOfflinetransition.QA Steps
Ping me & @heyjennahay to test!
Same as Tests - Note: tester must have the
vendorMatchingbeta enabledPR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionmainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari